/*
	jGrouse framework
	Copyright 2007 by Denis Riabtchik 
	All rights reserved.
	
	Licensed under modified BSD license. See license.txt for details
*/


/**
 * Useful utilities
 * @module jgrouse.coreutil
 */

jgrouse.module(
{
	name : 'jgrouse.coreutil',
	body : function(embed)
	{
		
		/**@scope jgrouse*/
		
		/**
			Copy all attributes from source to destination
			@function copyAll
			@param {Object} dst - object to receive attributes
			@param {Object} src - object that provides attributes
		*/
		jgrouse.copyAll = function(dst, src)
		{
			for (var i in src)
			{
				dst[i] = src[i];
			}
		}
		
		/**
			Copy new attributes from src to dst. New attributes are the ones that that are not defined in dst
			@function copyNew
			@param {Object} dst - object to receive attributes
			@param {Object} src - object that provides attributes
		*/
		jgrouse.copyNew = function(dst, src)
		{
			for (var i in src)
			{
				if (typeof dst[i] == 'undefined')
				{
					dst[i] = src[i];
				}
			}
		}
		
		/**
			Copy only data attributes from src to dst. Functions, etc are not included
			@function copyData
			@param {Object} dst - object to receive attributes
			@param {Object} src - object that provides attributes
		*/
		jgrouse.copyData = function(dst, src)
		{
			for (var i in src)
			{
				if (!(src[i] instanceof Function)) // using instanceof instead of typeof because typeof /regexp/ == 'function'
				{
					dst[i] = src[i];
				}
			}
		}
		
		/**
		  Signature of watcher function
		  @ifunction watcherFunction
		  @param {Object} dst - target object
		  @param {String} propertyName - name of changed property
		  @param {Object} propertyValue - new value of property
		  @param {Object} oldValue - old value of property
		*/
		
		/**
			Copy all changed attributes from src to dst and invoke callback for every changed attribute or every deleted attribute
			@function syncData
			@param {Object} dst - object to receive attributes
			@param {Object} src - object that provides attributes
			@param {watcherFunction} watcher - function to be called for every mismatch.	
		*/
		jgrouse.syncData = function(dst, src, watcher)
		{
			for (var i in src)
			{
				if (!(src[i] instanceof Function)) // using instanceof instead of typeof because typeof /regexp/ == 'function'
				{
					if (typeof dst[i] == undefined || dst[i] !== src[i])
					{
						var oldValue = dst[i];
						dst[i] = src[i];
						if (watcher)
						{
							watcher.onWatch(dst, i, dst[i], oldValue);
						}
					}
				}
			}
			for (var i in dst)
			{
				if (!(dst[i] instanceof Function))
				{
					if (typeof src[i] == 'undefined' && typeof dst[i] != 'undefined')
					{
						var oldValue = dst[i];
						dst[i] = src[i];
						if (watcher)
						{
							watcher.onWatch(dst, i, dst[i], oldValue);
						}
					}
				}
			}
		}
		
		/**
			Return a function that is bound to given object
			@function bind
			@paramset Option 1
			@param {Object} obj - object to be applied as 'this' for the function
			@param {String} methodName - name of method to be bound
			@param {Array} otherArgs - additional arguments that should be added when calling method
			@paramset Option 2
            @param {Object} obj - object to be applied as 'this' for the function
            @param {Function} method - function to be bound
            @param {Array} otherArgs - additional arguments that should be added when calling method
			@returns function bound to given object
		*/
		jgrouse.bind = function(obj, method, otherArgs)
		{
			var args = arguments.length <= 2? [] : jgrouse.toArray(arguments);
			args.splice(0, 2);
			var m = (typeof method == 'string')? obj[method] : method;
			return function()
			{
				return m.apply(obj, jgrouse.toArray(arguments).concat(args));
			}
		}
		/**
			Global handler for exceptions thrown in event listeners.
			Signature is function(event, exception)
			returns true if exception should be suppressed, otherwise it would be rethrown
			@variable eventExceptionHandler
		*/
		jgrouse.eventExceptionHandler = null;
		
		/**
			Return function that bound to given object has interface event listener - function(event)
			@function bindAsEventListener
			@paramset Option 1
			@param {Object} obj - object to be applied as 'this' for the function
			@param {String} methodName - name of method to be bound
			@param {Array} otherArgs - additional arguments that should be added when calling method
            @paramset Option 2
            @param {Object} obj - object to be applied as 'this' for the function
            @param {Function} method - function to be bound
            @param {Array} otherArgs - additional arguments that should be added when calling method
			
		*/
		jgrouse.bindAsEventListener = function(obj, method, otherArgs)
		{
			var args = arguments.length <= 2? [] : jgrouse.toArray(arguments);
			args.splice(0, 2);
			var m = (typeof method == 'string')? obj[method] : method;
			return function(event)
			{
				var ev = event || window.event;
				try
				{
					var res = m.apply(obj, [ev].concat(args));
					return res;
				}
				catch (ex)
				{
					if (jgrouse.eventExceptionHandler)
					{
						if (!jgrouse.eventExceptionHandler(ev, ex))
						{
							throw ex;
						}
					}
				}
			}	
		}
		
		/**
			Create methods in object that delegating to methods of given member.
			For example, if foo.bar.Bla has a member boo with method doThis , then delegating method would be 
			added foo.bar.Bla.doThis = function() {this.boo.doThis(arguments)}. Note: this call would copy only methods 
			that do not exist in dst.
			@function delegate
			@paramset Option 1
			@param {Object} dst - object that should be enriched. Typically would be prototype of class
			@param {String} memberName - name of member that is used as delegate
			@param {Object} memberClass - member's prototype (structure with all members of the delegate) 
			@param {optional String[]} exclude - array of member's method names that should not be delegated to dst 
            @paramset Option 1a
            @param {Object} dst - object that should be enriched. Typically would be prototype of class
            @param {String} memberName - name of member that is used as delegate
            @param {String} memberClassName - member's class name 
            @param {optional String[]} exclude - array of member's method names that should not be delegated to dst 
            @paramset Option 2
            @param {Object} dst - object that should be enriched. Typically would be prototype of class
            @param {Object} member - object that is used as delegate
            @param {Object} memberClass - member's prototype (structure with all members of the delegate) 
            @param {optional String[]} exclude - array of member's method names that should not be delegated to dst 
            @paramset Option 2a
            @param {Object} dst - object that should be enriched. Typically would be prototype of class
            @param {Object} member - name of member that is used as delegate
            @param {Object} memberClassName - member's prototype (structure with all members of the delegate) 
            @param {optional String[]} exclude - array of member's method names that should not be delegated to dst 
			
		*/
		jgrouse.delegate = function(dst, member, memberClass, exclude)
		{
			if (typeof memberClass == 'string')
			{
				memberClass = jgrouse.resolveName(memberClass).prototype;
			}
			var ex = ['superclass', 'initialize'].concat(exclude? exclude : []);
			for (var p in memberClass)
			{
				var f = memberClass[p];
				if (!dst[p] && f instanceof Function && jgrouse.search(ex, p) < 0)
				{
					dst[p] = function()
					{
						var mem = arguments.callee.member;				
						var func = arguments.callee.method;
						var m = (typeof mem == 'string')? this[mem] : mem;
						return func.apply(m, arguments);
					}
					dst[p].member = member;
					dst[p].method = f;
				}
			}
		}
		
		/**
			Compare two objects. If both objects have method equals then it is used for comparison
			@function objectComparator
			@param {Object} obj1 - first object to compare
			@param {Object} obj2 - second object
			@return true if first object equals to the second 
		*/
		
		jgrouse.objectComparator = function(obj1, obj2)
		{
			if (obj1 === obj2)
			{
				return true;
			}
			if (obj1 === undefined) 
			{
				return obj2 == undefined;
			}
			if (obj2 === undefined)
			{
				return false;
			}
			if (obj1.equals && obj2.equals)
			{
				return obj1.equals(obj2);
			}
			return false;
		}
		
		/**
		* Allow calling of function that accepts a number of parameters in Perl way.
		*	Example: <br>
		*	<pre><code>
		*   foo = jgrouse.perlify(function(arg1, arg2, arg3)
		*   {
		*     //do something
		*   }, ['arg1', 'arg2', 'arg3'])
		*   ...
		*   // invoke it the usual way
		*   foo('bebebe', 32, 'boo');
		*   // invoke it the Perl way
		*   foo({arg2:'boo', arg3:32, arg1:'bebebe'});
		*	</code></pre>
		*	@function {function} perlify
		*	@param {function} func - target function
		*	@param {String[]} argNames - names of arguments in order in which they appear in the target function
		*/
		jgrouse.perlify = function(func, argNames)
		{
			return new function()
			{
				if (arguments.length != 1 || (arguments.length == 1 && typeof arguments[0] != 'object'))
				{
					return func.apply(this, jgrouse.toArray(arguments));
				}
				else
				{
					var ar = [];
					var param = arguments[0];
					for (var i = 0; i < argNames.length; i++)
					{
						ar.push(param[argNames[i]]);
					}
					return func.apply(this, ar);
				}
			}
		}
		
		
		
		/**
			Checks if the structure is empty, i.e. does not have any members 
			@function isEmpty
			@param {Object} obj structure
			@returns true if structure has at least one member
		*/
		jgrouse.isEmpty = function(obj)
		{
			var dummy = {};
			for (var i in obj)
			{
				if (obj[i] != dummy[i])
				{
					return false;
				}
			}
			return true;
		}
		
		
		
		/**
			Converts argument to array, if possible. If data is array, then copy of data is returned. 
			If data has method toArray then the result of that method is returned. If data has property length, then it is assumed
			that it is possible to iterate over elements of data as if over array. If neither of these conditions are met, then 
			empty array is returned.
			@function toArray
			@param {Object} data - object that should be converted to array
			@returns result of conversion to array
		*/
		jgrouse.toArray = function(data)
		{
			if (data instanceof Array)
			{
				return data.concat([]);
			}
			if (data.toArray)
			{
				return data.toArray();
			}
			if (data.length)
			{
				var res = [];
				for (var i = 0; i < data.length; i++)
				{
					res.push(data[i]);
				}		
				return res;
			}
			return [];
		}
		
		
		/**
		  Signature of function that compares two objects
		  @ifunction {int} comparatorFunction
		  @param {Object} obj1 - first object
		  @param {Object} obj2 - second object
		  @return -1 if obj1 &lt; obj2; 0 if obj1 == obj2; 1 if obj1 &gt; obj2
		 */
		
		/**
			Searches for item in array 
			@function search
			@param {array} arr - array containing data
			@param {Object} item - item that is being searched for
			@param {optional comparatorFunction} comparator function 
			@returns the index where item was found or -1 otherwise
		*/
		
		jgrouse.search = function(arr, item, comparator)
		{
			// unwind the loop for performance
			if (comparator)
			{
				for (var i = arr.length - 1; i >= 0; i--)
				{
					if (comparator(item, arr[i]) === true)
					{
						return i;
					}
				}
			}
			else
			{
				for (var i = arr.length - 1; i >= 0; i--)
				{
					if (arr[i] == item)
					{
						return i;
					}
				}
			}
			return -1;
		}
		
		
	} 
});


